﻿using System;
using System.Text;
using System.IO;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;

namespace VSIXUtilities.Helpers
{
	//
	// TypeView.cs
	// finds and displays information about a type.
	//
	// Thu, 15 May 2003  10:05
	//
	//

	// see sample source at
	//  c:/Program Files/Microsoft.NET/FrameworkSDK/Samples/QuickStart/aspplus/samples/classbrowser

	public class myComparer : System.Collections.IComparer
	{
		// this is a smart comparer - used in sorting properties,
		// methods, and fields by name.  It works with MethodInfo,
		// PropertyInfo, and FieldInfo, as required, depending on
		// the type of the pass-in object.
		public int Compare(Object x, Object y)
		{
			Type t;
			t = typeof(MethodInfo);
			if(t.IsInstanceOfType(x))
			{
				MethodInfo i1 = (MethodInfo)x;
				MethodInfo i2 = (MethodInfo)y;
				return i1.Name.CompareTo(i2.Name);
			}

			t = typeof(PropertyInfo);
			if(t.IsInstanceOfType(x))
			{
				PropertyInfo i1 = (PropertyInfo)x;
				PropertyInfo i2 = (PropertyInfo)y;
				return i1.Name.CompareTo(i2.Name);
			}
			t = typeof(FieldInfo);
			if(t.IsInstanceOfType(x))
			{
				FieldInfo i1 = (FieldInfo)x;
				FieldInfo i2 = (FieldInfo)y;
				return i1.Name.CompareTo(i2.Name);
			}

			return 0;
		}
	}


	public class TypeView
	{
		public Boolean verbose = false;
		private Type mt = null;
        private Type TryLoad(String theTypeName, String theAssemblyString)
        {
            Type t = null;
            // load from a name like System.Data
            Assembly a = Assembly.Load(theAssemblyString);
            if (a == null)
                a = Assembly.LoadFrom(theAssemblyString);  // load from a physical filename 

            if (a != null)
                t = a.GetType(theTypeName, false, true);

            return t;  // maybe null
        }

        private void AppendParams(StringBuilder sb, ParameterInfo[] pi, bool isProperty)
        {
            int j;
            String openBracket = "(";
            String closeBracket = ")";
            if (isProperty)
            {
                openBracket = "[";
                closeBracket = "]";
            }
            for (j = 0; j < pi.Length; j++)
            {
                if (j == 0)
                    sb.Append(openBracket);
                else
                    sb.Append(", ");
                ParameterAttributes Attributes = pi[j].Attributes;
                if (Attributes != ParameterAttributes.None)
                    sb.Append(Attributes.ToString());
                sb.Append(pi[j].ParameterType.ToString()).Append(" ").Append(pi[j].Name);
            }
            if (isProperty)
            {
                if (j != 0)
                    sb.Append(closeBracket);
            }
            else
            {
                if (j == 0)
                    sb.Append(openBracket);
                sb.Append(closeBracket);
            }
            return;
        }
        private void EmitConstructors()
        {
            ConstructorInfo[] ci = mt.GetConstructors();
            Console.WriteLine("Constructors:\n");
            if ((ci == null) || (ci.Length == 0))
                Console.WriteLine("  -none-\n");
            else
            {
                StringBuilder sb1 = new StringBuilder();
                foreach (ConstructorInfo c in ci)
                {
                    try
                    {
                        sb1.Append("  ").Append(mt.Name);  // c.Name == .ctor
                        AppendParams(sb1, c.GetParameters(), false);
                        sb1.Append("\n");
                    }
                    catch (Exception e)
                    {
                        sb1.Append("Exception: " + e.Message);
                        continue;
                    }
                }
                Console.WriteLine(sb1.ToString());
            }

            return;
        }
        private String EmitMethodAttrs(MethodInfo m)
        {
            StringBuilder result = new StringBuilder();

            if (m.IsPublic)
                result.Append("public ");

            if (m.IsFamily)
                result.Append("protected ");

            if (m.IsPrivate)
                result.Append("private ");

            if (m.IsAbstract)
                result.Append("abstract ");

            if (m.IsStatic)
                result.Append("static ");

            if (m.IsFinal)
                result.Append("final ");

            return result.ToString();
        }
        private void EmitMethods()
        {
            MethodInfo[] mi = mt.GetMethods();
            Console.WriteLine("Methods:\n");
            if ((mi == null) || (mi.Length == 0))
                Console.WriteLine("  -none-\n");
            else
            {
                Array.Sort(mi, new myComparer());

                StringBuilder sb1 = new StringBuilder();

                foreach (MethodInfo m in mi)
                {
                    try
                    {
                        if (!m.IsSpecialName)
                        {
                            sb1.Append("  ").Append(EmitMethodAttrs(m));
                            sb1.Append("  ").Append(m.ReturnType.ToString());
                            sb1.Append("  ").Append(m.Name);
                            AppendParams(sb1, m.GetParameters(), false);
                            sb1.Append("\n");
                        }
                    }
                    catch (Exception e)
                    {
                        sb1.Append("Exception: " + e);
                        continue;
                    }

                }
                Console.WriteLine(sb1.ToString());
            }

            return;
        }
        private string TypeAttrsString(Type t)
        {
            StringBuilder sb = new StringBuilder();
            if (t.IsPublic)
                sb.Append("public ");
            if (t.IsAbstract)
                sb.Append("abstract ");
            if (t.IsSealed)
                sb.Append("sealed ");
            if (t.IsEnum)
                sb.Append("enum ");
            if (t.IsInterface)
                sb.Append("interface ");
            if (t.IsClass)
                sb.Append("class ");
            return sb.ToString();
        }
        private String PropAttrsString(PropertyInfo p)
        {
            StringBuilder result = new StringBuilder();
            MethodInfo mi = null;
            String access = null;

            if (p.GetGetMethod() != null)
            {
                mi = p.GetGetMethod();
                if (p.GetSetMethod() != null)
                    access = "read/write ";
                else
                    access = "readonly ";

            }
            else if (p.GetSetMethod() != null)
            {
                mi = p.GetSetMethod();
                access = "writeonly ";
            }

            if (mi != null)
            {
                if (mi.IsPublic)
                    result.Append("public ");
                if (mi.IsPrivate)
                    result.Append("private ");
                if (mi.IsFamily)
                    result.Append("protected ");

                if (mi.IsStatic)
                    result.Append("static ");
            }

            if (access != null)
                result.Append(access);

            return result.ToString();
        }
        private void EmitProperties()
        {
            PropertyInfo[] pi = mt.GetProperties();
            Console.WriteLine("Properties:\n");
            if ((pi == null) || (pi.Length == 0))
                Console.WriteLine("  -none-\n");
            else
            {
                Array.Sort(pi, new myComparer());  // by name
                StringBuilder sb1 = new StringBuilder();
                foreach (PropertyInfo p in pi)
                {
                    try
                    {
                        sb1.Append("  ").Append(PropAttrsString(p));
                        sb1.Append("  ").Append(p.PropertyType.ToString());
                        sb1.Append("  ").Append(p.Name);
                        AppendParams(sb1, p.GetIndexParameters(), true);
                        sb1.Append("\n");
                    }
                    catch (Exception e)
                    {
                        sb1.Append("Exception: " + e.Message);
                        continue;
                    }
                }
                Console.WriteLine(sb1.ToString());
            }
            return;
        }
        private String FieldAttrsString(FieldInfo f)
        {
            StringBuilder result = new StringBuilder();
            if (f.IsPublic)
                result.Append("public ");
            if (f.IsFamily)
                result.Append("protected ");
            if (f.IsPrivate)
                result.Append("private ");
            if (f.IsLiteral)
                result.Append("const ");
            if (f.IsStatic)
                result.Append("static ");
            return result.ToString();
        }
        private void EmitFields()
        {
            FieldInfo[] fi = mt.GetFields();
            Console.WriteLine("Fields:\n");
            if ((fi == null) || (fi.Length == 0))
                Console.WriteLine("  -none-\n");
            else
            {
                Array.Sort(fi, new myComparer());  // by name
                StringBuilder sb1 = new StringBuilder();

                foreach (FieldInfo f in fi)
                {
                    try
                    {
                        sb1.Append("  ").Append(FieldAttrsString(f));
                        sb1.Append("  ").Append(f.FieldType.ToString());
                        sb1.Append("  ").Append(f.Name).Append("\n");
                    }
                    catch (Exception e)
                    {
                        sb1.Append("Exception: " + e.Message);
                        continue;
                    }
                }
                Console.WriteLine(sb1.ToString());

            }
            return;
        }
		private System.Collections.Stack classHierarchy;
		private void BuildClassHierarchy(Type t)
		{
			classHierarchy.Push(t);
			if(t.BaseType != null)
				BuildClassHierarchy(t.BaseType);
		}
		private Type ReferencedType(Type t)
		{
			return (t.HasElementType) ? ReferencedType(t.GetElementType()) : t;
		}
		private String TypeStringWithInterfaces(Type t1)
		{

			StringBuilder result = new StringBuilder(t1.ToString());

			Type t = ReferencedType(t1);

			Type[] it = t.GetInterfaces();
			if(it.Length > 0)
			{
				int j = 0;
				for(int i = 0; i < it.Length; i++)
				{
					if((t.BaseType != null) && !(it[i].IsAssignableFrom(t.BaseType)))
					{
						if(j == 0) result.Append(" : ");
						else result.Append(", ");
						result.Append(it[i].ToString());
						j++;
					}
				}
			}

			return result.ToString();
		}

        public void EmitHierarchy()
        {
            if (mt == null)
                return;
            classHierarchy = new System.Collections.Stack();
            BuildClassHierarchy(mt);
            Console.WriteLine("Hierarchy:\n");
            StringBuilder sb = new StringBuilder();
            int i = 0;
            while (classHierarchy.Count != 0)
            {
                for (int j = 0; j < i + 1; j++)
                {
                    sb.Append("  ");
                }
                if (i != 0)
                    sb.Append("+ ");
                sb.Append(TypeStringWithInterfaces((Type)classHierarchy.Pop())).Append("\n");
                i++;
            }

            Console.WriteLine(sb.ToString());
            return;
        }
		private void DoOneType(Type t)
		{
			mt = t;
			Console.WriteLine("{0} {1}\n\nAssembly: {2}\n", TypeAttrsString(mt), mt.ToString(), mt.AssemblyQualifiedName);

			EmitHierarchy();
			EmitConstructors();
			EmitMethods();
			EmitProperties();
			EmitFields();
			//EmitEvents(); 
		}
        private void Run(String[] args)
        {

            string theTypeName;

            if (args.Length == 1)
            {
                if (System.IO.File.Exists(args[0]))
                {  // (args[0].Endswith(".dll"))
                    // display all types in a given dll
                    Assembly a = Assembly.LoadFrom(args[0]);
                    Type[] tset = a.GetTypes();
                    Console.WriteLine("Types contained in {0}:", args[0]);
                    foreach (Type t1 in tset)
                    {
                        Console.WriteLine("  " + t1.ToString());
                    }
                    Console.WriteLine("\n--------------------------------------------\n");
                    foreach (Type t1 in tset)
                    {
                        DoOneType(t1);
                    }
                }
                else
                {
                    // file with name arg[0] does not exist, so assume it is a typename
                    theTypeName = args[0];
                    // apply a heuristic here.  Try loading...Guess the assembly name: 
                    //  (1) mscorlib
                    //  (2) System
                    //  (3) an assembly name derived from a fully qualified typename
                    // then fail.

                    Type t = TryLoad(theTypeName, "mscorlib");

                    if (t == null)
                        t = TryLoad(theTypeName, "System");

                    if (t == null)
                    {
                        int p = theTypeName.LastIndexOf('.');
                        t = TryLoad(theTypeName, theTypeName.Substring(0, p));
                    }

                    if (t != null)
                        DoOneType(t);
                    else
                        Console.WriteLine("Cannot load a type by the name '{0}'", theTypeName);

                }
            }
            else
            {
                // more than one argument
                theTypeName = args[0];
                string theAssemblyName = args[1];
                Type t = TryLoad(theTypeName, theAssemblyName);
                if (t != null)
                    DoOneType(t);
                else
                    Console.WriteLine("Cannot load a type by the name '{0}'\n from assembly '{1}'",
                                 theTypeName, theAssemblyName);
            }

            return;
        }
		private static void DisplayUsage()
		{
			Console.WriteLine("usage::\n   TypeView.exe  <classname>  [-a <assemblyName>]");
		}
		public static int Main(string[] args)
		{

			if(args.Length < 1)
			{
				DisplayUsage();
				return 0;
			}

			TypeView c = new TypeView();
			c.Run(args);
			return 0;

		}
	}
}
